package com.zhy.ble.demo;

import android.app.ActivityManager;
import android.app.AlarmManager;
import android.app.Application;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;

import com.bryan.common.utils.AppManager;
import com.bryan.common.utils.log.LogUtils;

import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * Created by bryan on 2017/11/16 0016.
 */
public class CrashHandler implements Thread.UncaughtExceptionHandler {

    private Thread.UncaughtExceptionHandler mDefaultUncaughtExceptionHandler;

    /**
     * 存储异常和参数信息
     */
    private Map<String, String> paramsMap = new HashMap<>();

    /**
     * 格式化时间
     */
    private SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");

    private String TAG = this.getClass().getSimpleName();


    public static CrashHandler getInstance() {
        return CrashHandlerHolder.INSTANCE;
    }

    private MyApplication gApp;

    public void init(MyApplication context) {
        gApp = context;
        /*
         * 弹出解决方案之后把崩溃继续交给系统处理，
         * 所以保存当前UncaughtExceptionHandler用于崩溃发生时使用。
         */
        mDefaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
        Thread.setDefaultUncaughtExceptionHandler(this);
    }


    // 当有未截获的异常时，回调此方法
    @Override
    public void uncaughtException(Thread thread, Throwable ex) {

        if (!handleException(ex) && mDefaultUncaughtExceptionHandler != null) {
            // 如果用户没有处理则让系统默认的异常处理器来处理
            mDefaultUncaughtExceptionHandler.uncaughtException(thread, ex);
        } else {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }


            //销毁
            quitAppCompletely();
        }
    }


    /**
     * 完全退出App的方法
     * 由于Android系统自身机制，当App崩溃后，会自动根据斩里面的Activity的顺讯，重启栈顶的Activity
     * 有些时候，因为自动重启，会导致一些初始化没有走，带来其他bug，这里让App崩溃后自动关闭，不再重启
     * <p>
     * 1-首先要定义Activity的管理类，然后通过Activity的基类（或者ActivityLifecycleCallbacks，建议这个方法吧，避免一些不是自己写的，不继承基类）
     * 将所有的Activity加进去。在onDestroy中移除
     * 2-在崩溃的时候要finish掉所有的Activity
     * 3-然后杀死所有的Service
     * 4-再然后杀死App的进程。
     */
    public void quitAppCompletely() {
        try {
            //1-关闭所有的Activity
            AppManager.getAppManager().finishAllActivity();
            //2-停止掉所有的服务
            // TODO: 2019-08-27

            //这个是否能替代上面的逐个杀死服务，答案是不行,服务还是会继续重启
            ActivityManager activityMgr = (ActivityManager) gApp
                    .getSystemService(Context.ACTIVITY_SERVICE);
            if (activityMgr != null) {
                activityMgr.killBackgroundProcesses(gApp.getPackageName());
            }

            //杀死系统进程
            //调用系统API结束进程
            android.os.Process.killProcess(android.os.Process.myPid());
            //结束整个虚拟机进程，注意如果在manifest里用android:process给app指定了不止一个进程，则只会结束当前进程
            System.exit(0);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void restartApp(Application application, Class cls, long delayMillis) {
        Intent intent = new Intent(application.getApplicationContext(), cls);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        PendingIntent restartIntent = PendingIntent.getActivity(application.getApplicationContext(), 0, intent, 0);
        AlarmManager alarmManager = (AlarmManager) application.getSystemService(Context.ALARM_SERVICE);//延迟delayMillis毫秒执行操作
        alarmManager.set(AlarmManager.RTC, System.currentTimeMillis() + delayMillis, restartIntent);
        android.os.Process.killProcess(android.os.Process.myPid());
    }


    /**
     * 收集错误信息.发送到服务器
     *
     * @return 处理了该异常返回true, 否则false
     */
    private boolean handleException(Throwable ex) {
        if (ex == null) {
            return false;
        }
        //使用Toast来显示异常信息
        new Thread() {
            @Override
            public void run() {
                Looper.prepare();
                Toast.makeText(gApp, "很抱歉，程序出现异常，即将退出", Toast.LENGTH_SHORT).show();
                Looper.loop();
            }
        }.start();
        //收集设备参数信息
        collectDeviceInfo(gApp);
        //添加自定义信息
        addCustomInfo();
        //保存日志文件
        saveCrashInfo2File(ex);
        return true;
    }

    /**
     * 收集设备参数信息
     *
     * @param ctx
     */
    public void collectDeviceInfo(Context ctx) {
        //获取versionName,versionCode
        try {
            PackageManager pm = ctx.getPackageManager();
            PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), PackageManager.GET_ACTIVITIES);
            if (pi != null) {
                String versionName = pi.versionName == null ? "null" : pi.versionName;
                String versionCode = pi.versionCode + "";
                paramsMap.put("versionName", versionName);
                paramsMap.put("versionCode", versionCode);
            }
        } catch (PackageManager.NameNotFoundException e) {
            Log.e(TAG, "an error occured when collect package info", e);
        }
        //获取所有系统信息
        Field[] fields = Build.class.getDeclaredFields();
        for (Field field : fields) {
            try {
                field.setAccessible(true);
                paramsMap.put(field.getName(), field.get(null).toString());
            } catch (Exception e) {
                Log.e(TAG, "an error occured when collect crash info", e);
            }
        }
    }

    /**
     * 添加自定义参数
     */
    private void addCustomInfo() {

    }

    /**
     * 保存错误信息到文件中
     *
     * @param ex
     * @return 返回文件名称, 便于将文件传送到服务器
     */
    private String saveCrashInfo2File(Throwable ex) {

        StringBuffer sb = new StringBuffer();
        for (Map.Entry<String, String> entry : paramsMap.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            sb.append(key + "=" + value + "\n");
        }

        Writer writer = new StringWriter();
        PrintWriter printWriter = new PrintWriter(writer);
        ex.printStackTrace(printWriter);
        Throwable cause = ex.getCause();
        while (cause != null) {
            cause.printStackTrace(printWriter);
            cause = cause.getCause();
        }
        printWriter.close();
        String result = writer.toString();
        sb.append(result);
        try {
            String time = format.format(new Date());
            String fileName = time + ".txt";
            String logPath = MnsFileUtil.SDCARD_LOG_CRASH;
            FileOutputStream fos = new FileOutputStream(logPath + fileName);
            fos.write(sb.toString().getBytes());
            fos.close();
            //上传到bugly
            LogUtils.catchError("Crash", sb.toString(), ex);
            return fileName;
        } catch (Exception e) {
            Log.e(TAG, "an error occured while writing file...", e);
        }
        return null;
    }

    private static class CrashHandlerHolder {
        static final CrashHandler INSTANCE = new CrashHandler();
    }

    private CrashHandler() {
    }

}
